package ch.ethz.fcl.metrobuzz.data.roadnetwork;

import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.lwjgl.opengl.GL11;
import org.newdawn.slick.util.ResourceLoader;

import ch.ethz.fcl.metrobuzz.data.MBData;
import ch.ethz.fcl.metrobuzz.data.roadnetwork.hierarchy.RoadHierarchy;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Edge;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Node;
import ch.ethz.fcl.metrobuzz.data.singapore.SGMRTStations;

public class RoadNetwork {
	private MBData data;
	// record start and end edges for each node
	Map<Node, ArrayList<Integer>> endEdgeMap = null;
	Map<Node, ArrayList<Integer>> startEdgeMap = null;

	// record all passing nodes and edge in this road network
	ArrayList<Node> passingNodes = null;
	private List<Edge> passingEdges = null;

	private ArrayList<Node> startNodes = null;
	private ArrayList<Node> junctionNodes = null;

	private float bidirectionAngle = 0.99f;

	private RoadHierarchy hierarchy;

	public RoadNetwork(MBData data) {
		this.data = data;

		hierarchy = new RoadHierarchy(data);

		passingNodes = new ArrayList<Node>();
		endEdgeMap = new HashMap<Node, ArrayList<Integer>>();
		startEdgeMap = new HashMap<Node, ArrayList<Integer>>();
	}

	public void init() {
		startEdgeMap.clear();
		endEdgeMap.clear();

		initPassingEdges();
		initNodes();

		hierarchy.init();
	}

	/**
	 * read or calculate the edges that have traffic passing by
	 */
	private void initPassingEdges() {
		passingEdges = new ArrayList<Edge>();

		String fileName = "res/Singapore/weightedEdgeIndex";
		URL url = null;
		try {
			url = ResourceLoader.getResource(fileName);
		} catch (RuntimeException e) {
			fileName = "Singapore/weightedEdgeIndex";
			url = ResourceLoader.getResource(fileName);
		}

		// if file not exits, calculate the passing edges
		if (url == null) {
			try {
				FileWriter fw = new FileWriter(fileName);
				fw.write("Weighted Index \n");
				List<Edge> edges = data.getScene().getEdges();
				for (int i = 0; i < edges.size(); i++) {
					if (edges.get(i).getPassTripIds(0, 1).size() > 0) {
						fw.write(i + "\t");
						passingEdges.add(edges.get(i));
					}
					// only one passenger go to CG2:Changi Airport (from the
					// data set, very strange)
					else if (edges.get(i).getId().contains("CG2")) {
						if (!passingEdges.contains(edges.get(i))) {
							fw.write(i + "\t");
							passingEdges.add(edges.get(i));
						}
					}
				}

				fw.flush();
				fw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}

		} else { // if file exits, read from the file
			try {
				BufferedReader br = new BufferedReader(new InputStreamReader(
						ResourceLoader.getResourceAsStream(fileName)));
				br.readLine();
				String line = br.readLine();
				String[] index = line.split("\t");
				for (int i = 0; i < index.length; i++)
					passingEdges.add(data.getScene().getEdges()
							.get(Integer.parseInt(index[i])));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		// System.out.println("Num of weighted edges " + passingEdges.size());
	}

	/**
	 * initialize weights and degree for each node initialize start, junction
	 * nodes
	 */
	private void initNodes() {
		initPassingNodes();
		initNodeInOut();
		initStartNode();
		initJunctionNode();
	}

	private void initPassingNodes() {
		for (int i = 0; i < passingEdges.size(); i++) {
			Edge edge = passingEdges.get(i);
			Node start = data.getScene().getNode(edge.getStartNodeIndex());
			if (!passingNodes.contains(start)) {
				passingNodes.add(start);
			}

			Node end = data.getScene().getNode(edge.getEndNodeIndex());
			if (!passingNodes.contains(end)) {
				passingNodes.add(end);
			}

			ArrayList<Integer> startNodeToEdges = startEdgeMap.get(start);
			ArrayList<Integer> endNodeToEdges = endEdgeMap.get(end);

			if (startNodeToEdges == null) {
				ArrayList<Integer> startEdges = new ArrayList<Integer>();
				startEdges.add(i);
				startEdgeMap.put(start, startEdges);
			} else
				startNodeToEdges.add(i);

			if (endNodeToEdges == null) {
				ArrayList<Integer> endEdges = new ArrayList<Integer>();
				endEdges.add(i);
				endEdgeMap.put(end, endEdges);
			} else
				endNodeToEdges.add(i);
		}
	}

	/**
	 * init incoming and outgoing edges for each node
	 */
	private void initNodeInOut() {
		for (int i = 0; i < passingNodes.size(); i++) {
			Node node = passingNodes.get(i);

			ArrayList<Integer> startEdges = startEdgeMap.get(node);
			ArrayList<Integer> endEdges = endEdgeMap.get(node);
			for (int j = 0; startEdges != null && j < startEdges.size(); j++) {
				Edge edge = passingEdges.get(startEdges.get(j));
				node.addOut(edge);

				// check for bi-direction of each edge
				for (int k = 0; endEdges != null && k < endEdges.size(); k++) {
					Edge birdirEdge = passingEdges.get(endEdges.get(k));
					if (edge.getEndNodeIndex() == birdirEdge
							.getStartNodeIndex()) {
						edge.setBidirection(birdirEdge);
						birdirEdge.setBidirection(edge);
						break;
					}
				}
			}

			for (int j = 0; endEdges != null && j < endEdges.size(); j++) {
				Edge edge = passingEdges.get(endEdges.get(j));
				node.addIn(edge);
			}
		}
	}

	private void initStartNode() {
		startNodes = new ArrayList<Node>();
		for (int i = 0; i < passingNodes.size(); i++) {
			Node node = passingNodes.get(i);

			if (node.getDegree() == 1)
				startNodes.add(passingNodes.get(i));

			else if (node.getDegree() == 2) {
				List<Edge> outgoingEdges = node.getOutEdges();
				List<Edge> incomingEdges = node.getInEdges();

				if (outgoingEdges == null || outgoingEdges.size() != 1)
					continue;

				if (incomingEdges == null || incomingEdges.size() != 1)
					continue;

				if (outgoingEdges.get(0).cos(incomingEdges.get(0)) < bidirectionAngle)
					startNodes.add(node);
			}
		}
	}

	private void initJunctionNode() {
		junctionNodes = new ArrayList<Node>();
		for (int i = 0; i < passingNodes.size(); i++) {
			Node node = passingNodes.get(i);

			if (SGMRTStations.isMRTStation(node.getId())) {
				junctionNodes.add(node);
			} else {
				ArrayList<Integer> endEdgesIndex = endEdgeMap.get(node);
				ArrayList<Integer> startEdgesIndex = startEdgeMap.get(node);
				if (startNodes.contains(node))
					continue;

				if (node.getDegree() == 2)
					continue;

				else if (node.getDegree() == 3) {
					if (endEdgesIndex.size() == 1) {
						Edge end_edge = passingEdges.get(endEdgesIndex.get(0));
						Edge start_edge_0 = passingEdges.get(startEdgesIndex
								.get(0));
						Edge start_edge_1 = passingEdges.get(startEdgesIndex
								.get(1));
						if (end_edge.cos(start_edge_0) < bidirectionAngle
								|| end_edge.cos(start_edge_1) < bidirectionAngle)
							continue;
					}

					else if (endEdgesIndex.size() == 2) {
						Edge end_edge_0 = passingEdges
								.get(endEdgesIndex.get(0));
						Edge end_edge_1 = passingEdges
								.get(endEdgesIndex.get(1));
						Edge start_edge = passingEdges.get(startEdgesIndex
								.get(0));
						if (end_edge_0.cos(start_edge) < bidirectionAngle
								|| end_edge_1.cos(start_edge) < bidirectionAngle)
							continue;
					}
				}

				else if (node.getDegree() == 4) {
					if (endEdgesIndex.size() == 2) {
						Edge end_edge_0 = passingEdges
								.get(endEdgesIndex.get(0));
						Edge end_edge_1 = passingEdges
								.get(endEdgesIndex.get(1));
						Edge start_edge_0 = passingEdges.get(startEdgesIndex
								.get(0));
						Edge start_edge_1 = passingEdges.get(startEdgesIndex
								.get(1));
						if ((end_edge_0.cos(start_edge_0) < bidirectionAngle && end_edge_1
								.cos(start_edge_1) < bidirectionAngle)
								|| (end_edge_0.cos(start_edge_1) < bidirectionAngle && end_edge_1
										.cos(start_edge_0) < bidirectionAngle))
							continue;
					}
				}
				junctionNodes.add(node);
			}
		}
	}

	public float[][] getJunctionDistance() {
		float[][] junctionsDist = new float[junctionNodes.size()][junctionNodes
				.size()];
		for (int i = 0; i < junctionNodes.size(); i++)
			for (int j = 0; j < junctionNodes.size(); j++) {
				if (SGMRTStations.isMRTStation(junctionNodes.get(i).getId())
						|| SGMRTStations.isMRTStation(junctionNodes.get(j)
								.getId()))
					junctionsDist[i][j] = Float.MAX_VALUE;
				else
					junctionsDist[i][j] = junctionNodes.get(i).distance(
							junctionNodes.get(j));
			}

		return junctionsDist;
	}

	public RoadHierarchy getRoadHierarchy() {
		return hierarchy;
	}

	/**
	 * @param node
	 * @return indices of edges in the passing edges
	 */
	public ArrayList<Integer> getStartEdgeIndex(Node node) {
		return startEdgeMap.get(node);
	}

	/**
	 * 
	 * @param node
	 * @return indices of edges in the passing edges
	 */
	public ArrayList<Integer> getEndEdgeIndex(Node node) {
		return endEdgeMap.get(node);
	}

	public List<Edge> getPassingEdges() {
		return passingEdges;
	}

	public List<Node> getPassingNodes() {
		return passingNodes;
	}

	public ArrayList<Node> getJunctionNodes() {
		return junctionNodes;
	}

	public ArrayList<Node> getStartNodes() {
		return startNodes;
	}

	public void render() {
		GL11.glColor4f(0.3f, 0.2f, 0.2f, 0.3f);
		GL11.glLineWidth(1.0f);
		GL11.glBegin(GL11.GL_LINES);
		for (Edge edge : passingEdges) {
			GL11.glVertex3f(edge.getStartNode().getX(), edge.getStartNode()
					.getY(), 0);
			GL11.glVertex3f(edge.getEndNode().getX(), edge.getEndNode().getY(),
					0);
		}
		GL11.glEnd();
	}
}
